package com.atguigu.tingshu.common.login.aspect;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.login.annotation.TingshuLogin;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * <a href="https://www.shallowrecall.top/">Recall`Blog</a></br>
 * <a href="https://github.com/ShallowRecall">GitHub</a>
 *
 * @author Recall
 * @createTime 2025/03/12 21:23
 **/
@Aspect
@Component
@Slf4j
public class LoginAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 定义切面逻辑(判断请求的资源是否登录 如果登录 直接访问这个资源 反之去登录)
     */

    @Around("@annotation(com.atguigu.tingshu.common.login.annotation.TingshuLogin)")
    public Object loginCheck(ProceedingJoinPoint pjp) throws Throwable {


        // 1.获取请求中的令牌
        String jsonWebToken = getJsonWebToken();

        // 获取目标方法的注解以及属性值
        Boolean isLogin = getMethodLoginAnnotationValue(pjp);

        if (!isLogin){
            // 如果你登录了，即携带了token 那么也要把用户id传过来
            if (StringUtils.isEmpty(jsonWebToken)){
                return pjp.proceed();
            }
        }


        // 2.判断是否携带了令牌（如果携带令牌还将载荷中的数据获取到）
        Long userId = checkTokenAndGetUserId(jsonWebToken);
        Object retVal;
        // 将认证中心获取到的userId放到ThreadLocal中
        AuthContextHolder.setUserId(userId);
        try {
            // 执行目标方法
            retVal = pjp.proceed();
        } finally {
            AuthContextHolder.removeUserId();
        }

        // 返回通知
        return retVal;
    }

    private Boolean getMethodLoginAnnotationValue(ProceedingJoinPoint pjp) {

        // 1. 获取方法签名对象
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 2. 获取方法对象
        Method method = methodSignature.getMethod();
        // 3. 获取方法上的注解TingshuLogin注解对象
        TingshuLogin annotation = method.getAnnotation(TingshuLogin.class);
        // 4. 获取注解对象的属性值并返回
        return annotation.required();
    }

    private static String getJsonWebToken() {
        // 1. 获取用户的身份信息
        // 1.1 获取目标请求属性对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 1.2 获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();
        // 1.3 获取请求头中的token
        return request.getHeader("token");
    }

    private Long checkTokenAndGetUserId(String jsonWebTokenFromWeb) {

        // 1.判断请求中是否携带了
        if (StringUtils.isEmpty(jsonWebTokenFromWeb)) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        // 2.校验jsonWebToken是否被篡改过
        Jwt jwt = JwtHelper.decodeAndVerify(jsonWebTokenFromWeb, new RsaVerifier(PublicConstant.PUBLIC_KEY));

        // 3.校验通过 获取载荷数据
        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        Object userId = map.get("id");
        Object openId = map.get("openId");

        // 4.比对Redis中是否存在jsonWebToken
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
        String accessTokenFromRedis = redisTemplate.opsForValue().get(accessTokenKey);
        if (StringUtils.isEmpty(accessTokenFromRedis)) {
            throw new GuiguException(401, "accessToken已过期");
        }
        if (!accessTokenFromRedis.equals(jsonWebTokenFromWeb)) {
            throw new GuiguException(401, "用户身份不存在");
        }

        return Long.valueOf(userId.toString());
    }


}
